// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.

using System.Threading.Tasks;
using Duende.IdentityServer.Configuration;
using Duende.IdentityServer.Endpoints.Results;
using Duende.IdentityServer.Hosting;
using Duende.IdentityServer.ResponseHandling;
using Duende.IdentityServer.Services;
using Duende.IdentityServer.Validation;
using Duende.IdentityServer.Extensions;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using static Duende.IdentityServer.Constants;
using Duende.IdentityServer.Events;

namespace Duende.IdentityServer.Endpoints
{
    internal class BackchannelAuthenticationEndpoint : IEndpointHandler
    {
        private readonly IClientSecretValidator _clientValidator;
        private readonly IBackchannelAuthenticationRequestValidator _requestValidator;
        private readonly IBackchannelAuthenticationResponseGenerator _responseGenerator;
        private readonly IEventService _events;
        private readonly ILogger<BackchannelAuthenticationEndpoint> _logger;
        private readonly IdentityServerOptions _options;

        public BackchannelAuthenticationEndpoint(
            IClientSecretValidator clientValidator,
            IBackchannelAuthenticationRequestValidator requestValidator,
            IBackchannelAuthenticationResponseGenerator responseGenerator,
            IEventService events,
            ILogger<BackchannelAuthenticationEndpoint> logger,
            IdentityServerOptions options)
        {
            _clientValidator = clientValidator;
            _requestValidator = requestValidator;
            _responseGenerator = responseGenerator;
            _events = events;
            _logger = logger;
            _options = options;
        }

        public async Task<IEndpointResult> ProcessAsync(HttpContext context)
        {
            _logger.LogTrace("Processing backchannel authentication request.");

            // validate HTTP
            if (!HttpMethods.IsPost(context.Request.Method) || !context.Request.HasApplicationFormContentType())
            {
                _logger.LogWarning("Invalid HTTP request for backchannel authentication endpoint");
                return Error(BackchannelAuthenticationErrors.InvalidRequest);
            }

            return await ProcessTokenRequestAsync(context);
        }

        private async Task<IEndpointResult> ProcessTokenRequestAsync(HttpContext context)
        {
            _logger.LogDebug("Start backchannel authentication request.");

            // validate client
            var clientResult = await _clientValidator.ValidateAsync(context);

            if (clientResult.Client == null)
            {
                return Error(BackchannelAuthenticationErrors.InvalidClient);
            }

            // validate request
            var form = (await context.Request.ReadFormAsync()).AsNameValueCollection();
            _logger.LogTrace("Calling into backchannel authentication request validator: {type}", _requestValidator.GetType().FullName);
            var requestResult = await _requestValidator.ValidateRequestAsync(form, clientResult);

            if (requestResult.IsError)
            {
                await _events.RaiseAsync(new BackchannelAuthenticationFailureEvent(requestResult));
                return Error(requestResult.Error, requestResult.ErrorDescription);
            }

            // create response
            _logger.LogTrace("Calling into backchannel authentication request response generator: {type}", _responseGenerator.GetType().FullName);
            var response = await _responseGenerator.ProcessAsync(requestResult);

            await _events.RaiseAsync(new BackchannelAuthenticationSuccessEvent(requestResult));
            LogResponse(response, requestResult);

            // return result
            _logger.LogDebug("Backchannel authentication request success.");
            return new BackchannelAuthenticationResult(response);
        }

        private void LogResponse(BackchannelAuthenticationResponse response, BackchannelAuthenticationRequestValidationResult requestResult)
        {
            _logger.LogTrace("BackchannelAuthenticationResponse: {@response} for subject {subjectId}", response, requestResult.ValidatedRequest.Subject.GetSubjectId());
        }

        BackchannelAuthenticationResult Error(string error, string errorDescription = null)
        {
            return new BackchannelAuthenticationResult(new BackchannelAuthenticationResponse(error, errorDescription));
        }
    }
}
